﻿using Common;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms.DataVisualization.Charting;

namespace MuseUI.ObservableData
{
    public class ChartingHelper
    {
        private static Font _font = new Font("Segoe UI", 9, FontStyle.Regular);
        public static ChartArea InitialiseChartArea(String name, Double[] ranges, float[] position, bool isReversed)
        {
            var chartArea = new ChartArea(name);
            chartArea.BackColor = Color.White;

            chartArea.AxisX.MajorGrid.LineDashStyle = ChartDashStyle.Dash;
            chartArea.AxisX.MajorGrid.LineColor = Color.FromArgb(160, 160, 160);
            chartArea.AxisY.MajorGrid.LineDashStyle = ChartDashStyle.Dash;
            chartArea.AxisY.MajorGrid.LineColor = Color.FromArgb(160, 160, 160);
            chartArea.AxisX.LabelStyle.ForeColor = Color.Black;
            chartArea.AxisY.LabelStyle.ForeColor = Color.Black;
            chartArea.AxisX.LabelStyle.Font = _font;
            chartArea.AxisY.LabelStyle.Font = _font;
            chartArea.AxisX.Minimum = ranges[0];
            chartArea.AxisX.Maximum = ranges[1];
            chartArea.AxisY.Minimum = ranges[2];
            chartArea.AxisY.Maximum = ranges[3];
            chartArea.AxisX.IsReversed = isReversed;

            chartArea.Position = new ElementPosition(position[0], position[1], position[2], position[3]);

            return chartArea;
        }

        public static void SetChartMinMax(Chart chart, ChartArea chartArea, List<String> excludes = null, Double? minimum = null, Double? maximum = null, Double precision = 0.01)
        {
            Double[] displayPeriod;

            Double xMin = Double.MaxValue;
            Double xMax = -Double.MaxValue;

            if (!Double.IsNaN(chartArea.AxisX.ScaleView.ViewMinimum) && !Double.IsNaN(chartArea.AxisX.ScaleView.ViewMaximum))
            {
                if (chartArea.AxisX.ScaleView.ViewMinimum < xMin && chartArea.AxisX.ScaleView.ViewMinimum != 0)
                    xMin = chartArea.AxisX.ScaleView.ViewMinimum;
                if (chartArea.AxisX.ScaleView.ViewMaximum > xMax && chartArea.AxisX.ScaleView.ViewMaximum != 1)
                    xMax = chartArea.AxisX.ScaleView.ViewMaximum;
            }

            if (xMin == Double.MaxValue)
                xMin = -Double.MaxValue;
            if (xMax == -Double.MaxValue)
                xMax = Double.MaxValue;

            displayPeriod = new Double[] { xMin, xMax };

            SetChartMinMax(chart, chartArea, displayPeriod, excludes, minimum, maximum, precision);
        }

        public static void SetChartMinMax(Chart chart, ChartArea chartArea, Double[] displayPeriod, List<String> excludes = null, Double? minimum = null, Double? maximum = null, Double precision = 0.01)
        {
            int minIndex = 0;
            int maxIndex = 1;

            bool getXMinAndMax = (displayPeriod[0] == -Double.MaxValue);

            Double[] minMaxVals = new Double[] { Double.MaxValue, -Double.MaxValue, Double.MaxValue, -Double.MaxValue };
            Double[] roundedMinMaxVals = new Double[] { Double.MaxValue, -Double.MaxValue, Double.MaxValue, -Double.MaxValue };

            Double xMin = Double.MaxValue;
            Double xMax = -Double.MaxValue;

            foreach (Series series in chart.Series)
            {
                if (series.ChartArea != chartArea.Name || (excludes != null && excludes.Contains(series.Name)))
                    continue;

                if (series.YAxisType == AxisType.Primary)
                {
                    minIndex = 0;
                    maxIndex = 1;
                }
                else
                {
                    minIndex = 2;
                    maxIndex = 3;
                }

                foreach (DataPoint dp in series.Points)
                {
                    if (dp.XValue <= displayPeriod[0] || dp.XValue >= displayPeriod[1])
                        continue;

                    if (getXMinAndMax)
                    {
                        if (dp.XValue < xMin)
                            xMin = dp.XValue;
                        if (dp.XValue > xMax)
                            xMax = dp.XValue;
                    }

                    if (dp.YValues[0] < minMaxVals[minIndex])
                        minMaxVals[minIndex] = dp.YValues[0];
                    if (series.YValuesPerPoint == 2 && dp.YValues[1] < minMaxVals[minIndex])
                        minMaxVals[minIndex] = dp.YValues[1];
                    if (dp.YValues[0] > minMaxVals[maxIndex])
                        minMaxVals[maxIndex] = dp.YValues[0];
                    if (series.YValuesPerPoint == 2 && dp.YValues[1] > minMaxVals[maxIndex])
                        minMaxVals[maxIndex] = dp.YValues[1];
                }
            }

            var absMin = Math.Abs(minMaxVals[minIndex]);
            var absMax = Math.Abs(minMaxVals[maxIndex]);

            var correction = (absMin > absMax) ? absMin * 0.0612 : absMax * 0.0612;
            roundedMinMaxVals[minIndex] = MathExtensions.Floor(minMaxVals[minIndex] - correction, precision);
            roundedMinMaxVals[maxIndex] = MathExtensions.Ceiling(minMaxVals[maxIndex] + correction, precision);

            if (minimum != null) roundedMinMaxVals[minIndex] = MathExtensions.Cap(roundedMinMaxVals[minIndex], minimum.Value, null);
            if (maximum != null) roundedMinMaxVals[maxIndex] = MathExtensions.Cap(roundedMinMaxVals[maxIndex], null, maximum.Value);

            if (roundedMinMaxVals[minIndex] == roundedMinMaxVals[maxIndex])
            {
                roundedMinMaxVals[maxIndex] += 0.01;
            }

            if (roundedMinMaxVals[0] == Double.MaxValue || roundedMinMaxVals[0] == Double.PositiveInfinity || roundedMinMaxVals[0] == Double.NegativeInfinity)
                roundedMinMaxVals[0] = Double.NaN;
            if (roundedMinMaxVals[2] == Double.MaxValue || roundedMinMaxVals[2] == Double.PositiveInfinity || roundedMinMaxVals[2] == Double.NegativeInfinity)
                roundedMinMaxVals[2] = Double.NaN;
            if (roundedMinMaxVals[1] == -Double.MaxValue || roundedMinMaxVals[1] == Double.PositiveInfinity || roundedMinMaxVals[1] == Double.NegativeInfinity)
                roundedMinMaxVals[1] = Double.NaN;
            if (roundedMinMaxVals[3] == -Double.MaxValue || roundedMinMaxVals[3] == Double.PositiveInfinity || roundedMinMaxVals[3] == Double.NegativeInfinity)
                roundedMinMaxVals[3] = Double.NaN;

            chartArea.AxisY.Minimum = roundedMinMaxVals[0];
            chartArea.AxisY.Maximum = roundedMinMaxVals[1];
            chartArea.AxisY2.Minimum = roundedMinMaxVals[2];
            chartArea.AxisY2.Maximum = roundedMinMaxVals[3];

            if (getXMinAndMax)
            {
                displayPeriod[0] = xMin;
                displayPeriod[1] = xMax;
            }
        }
    }
}
